test

12.2 JMXMAPI

JRockit中的另一种Management API就是JMXMAPI,它可看作是给予JMX的JMAPI,两者虽然不是一一对应,但总体上差不多。目前,JMXMAPI还不是官方支持的,可能会在将来的版本中有较大变动。

在JRockit的每个发行版中,JMXMAPI中MBean的域名都会发生变化。在R28版本中,由于Oracle收购BEA,它又变了。 估计最近一段时间Oracle不太可能被收购,所以期望这次域名能保持较长一段时间。起初,JMXMAPI的MBean是与java.lang.management域中的MBean(参见第7章的内容)放在一起的,后来把它放到了bea.jrockit.management域下,最后有把它已到了oracle.jrockit.management域下。若想以版本无关的方式访问JMX,可以使用第7章中介绍的 RJMX代理层来实现。

若想访问JMXMAPI,就必须要先载入JRockitConsoleMBean,具体来说,可以通过MBeanServerConnection来编程实现。

在R27.x版本中,可以是这样:

someMBeanServerConnection.createMBean("bea.jrockit.management.JRockitConsole", null);

在R28.x版本中,则是这样:

someMBeanServerConnection.createMBean("oracle.jrockit.management.JRockitConsole", null);

有了Management Console的代理层,这一切就可以自动完成了。

各个MBean是按照功能来分组的。在R28版本中,还可以自动创建JFR所需要的MBean。

在下面的表格中,介绍了可用的MBean:

                    JMXMAPI MBeans
MBean name                  Description
Compilation                 有关JIT编译器的信息
DiagnosticCommand           访问JVM内部诊断命令,参见第11章相关内容
GarbageCollector            有关垃圾回收器的相关信息,可以对垃圾回收器进行调整
JRockitConsole              Management Console的相关功能,例如转储堆。创建该MBean时会实例化并注册相关的API
Log                         控制JRockit日志模块
Memleak                     控制Memleak服务器
Memory                      访问物理内存
PerfCounters                这是一个动态生成的MB额按,可以列出所有的内部性能计数器
Profiler                    控制方法分析器
Runtime                     可以获取CPU信息,CPU负载,以及控制CPU亲和性
Threading                   获取县城相关信息。目前只包含操作,还没有属性可用

在R28版本中,还可以通过基于JMX的API来启动/控制JFR。该MBean在com.oracle.jrockit域下,起入口点在MBeanFlightRecorder中。它并不属于JMXMAPI。

12.2.1 JRockit内部性能计数器

大部分JMXMAPI都是通过MBean实现暴露出来的,而PerfCountersMBean则是动态生成的。JRockit在内部会使用一系列性能计数器来完成分析和诊断操作。每个JRockit内部性能计数器都对应了PerfCountersMBean中的一个属性。

由于JMXMAPI不受支持,因此连带着动态生成的PerfCountersMBean也得不到支持。不过,它们之间还是有区别的,jrockit.*包下的计数器比oracle.*包下的计数器更不受待见,支持更少。

下表介绍了在4.0/R28.x版本(在写书时,共有139种计数器)中最重要的几种计数器:

Counter                                         Description
java.cls.loadedClasses                          启动JVM之后,共载入了多少类
java.cls.unloadedClasses                        启动JVM之后,共卸载了多少类
java.property.java.class.path                   JVM的CLASSPATH路径
java.property.java.endorsed.dirs                有关`endorsed`目录的说明,请参见http://docs.oracle.com/javase/6/docs/中, **Endorsed Standards Override Mechanism**节的说明
java.property.java.ext.dirs                     扩展目录中的jar包会被自动添加到CLASSPATH中,参见JavaDoc中队`java.ext.dirs`的说明。更多详细内容,请参见http://java.sun.com/j2se/1.4.2/docs/guide/extensions/spec.html
java.property.java.home                         JDK或JRE的安装路径
java.property.java.library.path                 用户库的路径
java.property.java.vm.version                   JRockit版本号
java.rt.vmArgs                                  JVM参数
java.threads.daemon                             守护线程数目
java.threads.live                               运行中的线程的数目
java.threads.livePeak                           JVM启动后,同时运行的线程数目的峰值
java.threads.nonDaemon                          运行中的、非守护线程的数目
java.threads.started                            JRockit启动后,开启的线程的总数目
jrockit.gc.latest.heapSize                      当前堆大小,单位为字节
jrockit.gc.latest.nurserySize                   当前年轻代大小,单位为字节
jrockit.gc.latest.oc.compaction.time            上一次执行内存整理所花费的时间,单位为系统滴答数(ticks),如果想跳过内存整理操作,可将之置为0
jrockit.gc.latest.oc.heapUsedAfter              上一次老年代垃圾回收结束之后,堆中已使用的内存总量,单位为字节
jrockit.gc.latest.oc.heapUsedBefore             上一次老年代垃圾回收开始之前,堆中已使用的内存总量,单位为字节
jrockit.gc.latest.oc.number                     到目前为止,所执行过的老年代垃圾回收的次数
jrockit.gc.latest.oc.sumOfPauses                上一次老年代垃圾回收中应用程序的暂停时间,单位为系统滴答数(ticks)
jrockit.gc.latest.oc.time                       上一次老年代垃圾回收的持续时间,单位为系统滴答数(ticks)
jrockit.gc.latest.yc.sumOfPauses                上一次年轻代垃圾回收中应用程序的暂停时间,单位为系统滴答数(ticks)
jrockit.gc.latest.yc.time                       上一次年轻代垃圾回收的持续时间,单位为系统滴答数(ticks)
jrockit.gc.max.oc.individualPause               到目前为止,持续时间最长的老年代垃圾回收的持续时间,单位为系统滴答数(ticks)
jrockit.gc.max.yc.individualPause       到目前为止,持续时间最长的年轻代垃圾回收的持续时间,单位为系统滴答数(ticks)
jrockit.gc.total.oc.compaction.externalAborted  到目前为止,被终止的外部整理的次数
jrockit.gc.total.oc.compaction.internalAborted  到目前为止,被终止的内部整理的次数
jrockit.gc.total.oc.compaction.internalSkipped  到目前为止,被跳过的内部整理的次数
jrockit.gc.total.oc.compaction.time             到目前为止,执行内存整理所花费的总时间,单位为系统滴答数(ticks)
jrockit.gc.total.oc.ompaction.externalSkipped   到目前为止,被跳过的内部整理的次数
jrockit.gc.total.oc.pauseTime                   到目前为止,所有老年代垃圾回收导致应用程序暂停的总时间,单位为系统滴答数(ticks)
jrockit.gc.total.oc.time                        到目前为止,花费在老年代垃圾回收的总时间,单位为系统滴答数(ticks)
jrockit.gc.total.pageFaults                     到目前为止,在垃圾回收过程中所遇到的页错误(page fault)的总数目
jrockit.gc.total.yc.pauseTime                   到目前为止,所有年轻代垃圾回收导致应用程序暂停的总时间,单位为系统滴答数(ticks)
jrockit.gc.total.yc.promotedObjects             到目前为止,从年轻代提升到老年代的对象的总数目
jrockit.gc.total.yc.promotedSize                到目前为止,从年轻代提升到老年代的对象的总字节数
jrockit.gc.total.yc.time                        到目前为止,花费在年轻代垃圾回收的总时间,单位为系统滴答数(ticks)
oracle.ci.jit.count                             到目前为止,经过JIT编译的方法总数
oracle.ci.jit.timeTotal                         到目前为止,花费在JIT编译上的总时间,单位为系统滴答数(ticks)
oracle.ci.opt.count                             到目前为止,有多少方法被优化过
oracle.ci.opt.timeTotal                         到目前为止,花费在优化上的总时间,单位为系统滴答数(ticks)
oracle.rt.counterFrequency                      用于将系统滴答数转换为以秒为单位的时间

这些计时器都非常有用,需要注意的是,不少计时器是以系统滴答数为单位的,使用时,需要先将之转换为以秒为单位(将滴答数除以计数器oracle.rt.counterFrequency的值即可)。

MBean Browser中,编辑表设置,显示出 Description字段内容,以便查询哪些计数器是以系统滴答数为单位的。如下所示:

Figure 12-2

Description字段末尾会列出计时器的单位(如上图所示)。

12.2.2 使用JMXMAPI构建可远程操作的JRCMD

JMXMAPI的底层实现的JMX(参见第7章的相关内容),因此,可以很容的通过平台MBean服务器和标准远程JMX代理来访问远程服务器的MBean。正如第11章中介绍的,JRCMD只能对连接到本地服务器上的JVM,而且要求JRCMD和JVM的启动用户是同一人。相比之下,使用JMXMAPI就可以克服这些限制,实现远程访问。

import java.lang.management.ManagementFactory;
import java.net.MalformedURLException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.management.Attribute;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;

/**
 * Simple code example on how to execute
 * ctrl-break handlers remotely.
 *
 * Usage:
 * RemoteJRCMD -host -port -user -pass -command []
 *
 * All arguments are optional. If no command is
 * specified, all performance counters and their
 * current values are listed.
 *
 * @author Marcus Hirt
 */
public final class RemoteJRCMD {
    private final static String KEY_CREDENTIALS = "jmx.remote.credentials";
    private final static String JROCKIT_PERFCOUNTER_MBEAN_NAME = "oracle.jrockit.management:type=PerfCounters";
    private final static String JROCKIT_CONSOLE_MBEAN_NAME = "oracle.jrockit.management:type=JRockitConsole";
    private final static String[] SIGNATURE = new String[] {"java.lang.String"};
    private final static String DIAGNOSTIC_COMMAND_MBEAN_NAME = "oracle.jrockit.management:type=DiagnosticCommand";

    public static void main(String[] args) throws Exception {
        HashMap<String, String> commandMap = parseArguments(args);
        executeCommand(
            commandMap.get("-host"),
            Integer.parseInt(commandMap.get("-port")),
            commandMap.get("-user"),
            commandMap.get("-password"),
            commandMap.get("-command"));
    }

    private static HashMap<String, String> parseArguments(String[] args) {
        HashMap<String, String> commandMap = new HashMap<String, String>();
        commandMap.put("-host", "localhost");
        commandMap.put("-port", "7091");
        for (int i = 0; i < args.length; i++) {
            if (args[i].startsWith("-")) {
                StringBuilder buf = new StringBuilder();
                int j = i + 1;
                while (j < args.length && !args[j].startsWith("-")) {
                    buf.append(" ");
                    buf.append(args[j++]);
                }
                commandMap.put(args[i], buf.toString().trim());
                i = j - 1;
            }
        }
        return commandMap;
    }

    @SuppressWarnings("unchecked")
    public static void executeCommand(String host, int port, String user, String password, String command) throws Exception {
        MBeanServerConnection server = null;
        JMXConnector jmxc = null;
        Map<String, Object> map = null;
        if (user != null || password != null) {
            map = new HashMap<String, Object>();
            final String[] credentials = new String[2];
            credentials[0] = user;
            credentials[1] = password;
            map.put(KEY_CREDENTIALS, credentials);
        }
        // Use same convention as Sun. localhost:0 means
        // "VM, monitor thyself!"
        if (host.equals("localhost") && port == 0) {
            server = ManagementFactory.getPlatformMBeanServer();
        } else {
            jmxc = JMXConnectorFactory.newJMXConnector(
            createConnectionURL(host, port), map);
            jmxc.connect();
            server = jmxc.getMBeanServerConnection();
        }
        System.out.println("Connected to " + host+ ":" + port);
        try {
            server.getMBeanInfo(new ObjectName(JROCKIT_CONSOLE_MBEAN_NAME));
        } catch (InstanceNotFoundException e1) {
            server.createMBean("oracle.jrockit.management.JRockitConsole", null);
        }
        if (command == null) {
            ObjectName perfCounterObjectName = new ObjectName(JROCKIT_PERFCOUNTER_MBEAN_NAME);
            System.out.println("Listing all counters...");
            MBeanAttributeInfo[] attributes = server.getMBeanInfo(perfCounterObjectName).getAttributes();
            System.out.println("Counter\tValue\n=======\t====");
            String[] attributeNames = new String[attributes.length];
            for (int i = 0; i < attributes.length; i++) {
                attributeNames[i] = attributes[i].getName();
            }
            Iterator valueIter = server.getAttributes(
            perfCounterObjectName,
            attributeNames).iterator();
            while (valueIter.hasNext()) {
                Attribute attr = (Attribute) valueIter.next();
                System.out.println(attr.getName() + "\t=\t" + attr.getValue());
            }
        } else {
            System.out.println("Invoking the ctrl-break command '" + command + "'...");
            ObjectName consoleObjectName = new ObjectName(DIAGNOSTIC_COMMAND_MBEAN_NAME);
            Object[] params = new Object[1];
            params[0] = command;
            System.out.println("The CtrlBreakCommand returned: \n" + server.invoke(consoleObjectName, "execute", params, SIGNATURE));
        }
        if (jmxc != null) {
            jmxc.close();
        }
    }

    private static JMXServiceURL createConnectionURL(String host, int port) throws MalformedURLException {
        return new JMXServiceURL("rmi", "", 0, "/jndi/rmi://" + host + ":" + port + "/jmxrmi");
    }
}

使用方式如下所示:

java RemoteJRCMD –command <command string> -host <host> -port <port>

其中:

  • 是JRCMD中的诊断命令机器参数,例如start_flightrecording name=MyRecording duration=30s
  • 是目标JRockit JVM所在的主机地址,例如localhost
  • 是目标JRockit JVM中JMX代理(RMI Registry)所监听端口。更多详细内容,请参见第6章相关内容。默认情况下,端口号是7091。

下面的示例会列出所有性能计数器:

java RemoteJRCMD

下面的示例会列出主机bisty,端口4711中JRockit JVM所支持的所有诊断命令:

java RemoteJRCMD -command help -host bitsy -port 4711

下面的示例会在目标JVM中开启一个为期30秒的记录任务,并记录结束后将记录内容写入到指定文件中:

java RemoteJRCMD -command start_flightrecording name=myrecording filename=c:\tmp\myrecording.jfr

duration=30s